﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SimplexEngine
{
    public class Node : IDisposable
    {
        // constructor
        public Node() : this(name_generator.Generate()) { }

        // constructor with node name
        public Node(String node_name) 
        {
            name = node_name;
        }

        // distructor
        ~Node() 
        {
            Dispose(false);
        }

        // name
        protected String name;
        public String Name { get { return name; } }

        #region children operation
        // children
        protected Dictionary<String, Node> children = new Dictionary<String, Node>();
        public virtual Node GetChild(String child_name) 
        {
            return children[child_name];
        }

        public virtual Node GetChild(int index)
        {
            return children.Values.ElementAt(index);
        }

        public virtual Node RemoveChild(int index)
        {
            return RemoveChild(children.Keys.ElementAt(index));
        }

        public virtual Node RemoveChild(String child_name)
        { 
            Node child = null;
            if (!children.TryGetValue(child_name, out child))
                return null;

            children.Remove(child_name);
            child.Parent = null;
            return child;
        }

        public virtual Node RemoveChild(Node child)
        {
            if (child != null && children[child.Name] == child)
                return RemoveChild(child.Name);

            return null;
        }

        public virtual void AddChild(Node child)
        {
            if (child.Parent != null)
                child.Parent.RemoveChild(child);

            children.Add(child.Name, child);
            child.Parent = this;
        }

        // name generator
        protected static NameGenerator name_generator = new NameGenerator("Node");

        // parent
        protected Node parent = null;
        // get parent
        public Node Parent 
        { 
            get { return parent; }
            // call by parent
            protected set 
            {
                parent = value;
                NeedUpdate();
            }
        }
        #endregion

        #region transformation
        /// <summary>
        /// local scale
        /// </summary>
        virtual public Vector3 Scale
        {
            get { return scale; }
            set
            {
                scale = value;
                NeedUpdate();
            }
        }
        protected Vector3 scale = Vector3.One;

        /// <summary>
        /// local rotation
        /// </summary>

        virtual public Quaternion Rotation
        {
            get { return rotation; }
            set
            {
                rotation = value;
                NeedUpdate();
            }
        }
        protected Quaternion rotation = Quaternion.Identity;

        /// <summary>
        /// local translation
        /// </summary>
        virtual public Vector3 Translation
        {
            get { return translation; }
            set
            {
                translation = value;
                NeedUpdate();
            }
        }
        protected Vector3 translation = Vector3.Zero;

        /// <summary>
        /// update transform if cached is out of date
        /// </summary>
        virtual public void UpdateTransform()
        {
            // out of date?
            if (cached_transform_out_of_date)
            {
                // create transform matrix
                cached_transform = Matrix.CreateScale(WorldScale) * Matrix.CreateFromQuaternion(WorldRotation) * Matrix.CreateTranslation(WorldTranslation);
                // up to date
                cached_transform_out_of_date = false;
            }
        }

        /// <summary>
        /// get world transform
        /// </summary>
        virtual public Matrix Transform
        {
            get
            {
                // update transform
                UpdateTransform();
                // return transform
                return cached_transform;
            }
        }

        /// <summary>
        /// is this node inherit parent node's scale
        /// </summary>
        virtual public Boolean InheritScale
        {
            get { return inherit_scale; }
            set
            {
                // value have changed?
                if (inherit_scale != value)
                {
                    inherit_scale = value;
                    NeedUpdate();
                }
            }
        }
        protected Boolean inherit_scale = true;

        /// <summary>
        /// world scale
        /// </summary>
        virtual public Vector3 WorldScale
        {
            get
            {
                // need update?
                if (world_srt_out_of_date)
                    UpdateWorldSRT();
                return world_scale;
            }
        }
        protected Vector3 world_scale = Vector3.One;

        /// <summary>
        /// is this node inherit parent node's rotation
        /// </summary>
        protected Boolean inherit_rotation = true;
        virtual public Boolean InheritRotation
        {
            get { return inherit_rotation; }
            set
            {
                // value have changed?
                if (inherit_rotation != value)
                {
                    inherit_rotation = value;
                    NeedUpdate();
                }
            }
        }

        /// <summary>
        /// world rotation
        /// </summary>
        protected Quaternion world_rotation = Quaternion.Identity;
        virtual public Quaternion WorldRotation
        {
            get
            {
                // need update?
                if (world_srt_out_of_date)
                    UpdateWorldSRT();
                return world_rotation;
            }
        }

        /// <summary>
        /// world translation
        /// </summary>
        virtual public Vector3 WorldTranslation
        {
            get
            {
                // need update?
                if (world_srt_out_of_date)
                    UpdateWorldSRT();
                return world_translation;
            }
        }
        protected Vector3 world_translation = Vector3.Zero;

        /// <summary>
        /// update world scale, rotation and translation
        /// </summary>
        virtual protected void UpdateWorldSRT()
        {
            // srt out of date?
            if (world_srt_out_of_date)
            {
                // have parent
                if (Parent != null)
                {
                    // inherit scale?
                    if (InheritScale)
                        world_scale = Parent.WorldScale * scale;
                    else
                        // use local
                        world_scale = scale;

                    // inherit rotation?
                    if (InheritRotation)
                        world_rotation = Parent.WorldRotation * rotation;
                    else
                        world_rotation = rotation;

                    world_translation = Vector3.Transform(translation, Parent.Transform);
                }
                else
                {
                    // use local
                    world_scale = scale;
                    world_rotation = rotation;
                    world_translation = translation;
                }

                // up to date
                world_srt_out_of_date = false;
            }
        }

        /// <summary>
        /// transform a vector by rotation
        /// </summary>
        /// <param name="direction">vector need to transform</param>
        /// <returns>a transformed normalized vector</returns>
        public Vector3 TransformDirection(Vector3 direction)
        {
            return Vector3.Normalize(Vector3.Transform(direction, Matrix.CreateFromQuaternion(WorldRotation)));
        }

        // cached transform
        protected Matrix cached_transform = Matrix.Identity;
        // world srt up to date?
        protected Boolean world_srt_out_of_date = true;
        // cached transform up to date?
        protected Boolean cached_transform_out_of_date = true;
        #endregion

        #region directions
        /// <summary>
        /// the up vector of this node
        /// </summary>
        public Vector3 Up { get { return TransformDirection(Vector3.Up); } }

        /// <summary>
        /// the down vector of this node
        /// </summary>
        public Vector3 Down { get { return TransformDirection(Vector3.Down); } }

        /// <summary>
        /// the left vector of this node
        /// </summary>
        public Vector3 Left { get { return TransformDirection(Vector3.Left); } }

        /// <summary>
        /// the right vector of this node
        /// </summary>
        public Vector3 Right { get { return TransformDirection(Vector3.Right); } }

        /// <summary>
        /// the forward vector of this node
        /// </summary>
        public Vector3 Forward { get { return TransformDirection(Vector3.Forward); } }

        /// <summary>
        /// the backward vector of this node
        /// </summary>
        public Vector3 Backward { get { return TransformDirection(Vector3.Backward); } }

        #endregion

        #region update and draw

        /// <summary>
        /// call when this node need to update
        /// </summary>
        protected virtual void NeedUpdate()
        {
            // set cached transform out of date
            cached_transform_out_of_date = true;
            // world srt out of date
            world_srt_out_of_date = true;

            // notify child nodes to update
            foreach (Node node in children.Values)
                node.NeedUpdate();
        }

        public virtual Boolean Enable { get { return enable; } set { enable = value; } }
        protected Boolean enable = true;

        public virtual void Update(GameTime game_time, Int64 frame_number)
        {
            if (!Enable)
                return;

            // update transform
            UpdateTransform();
            // on update
            OnUpdate(game_time, frame_number);

            foreach (Node node in children.Values)
                node.Update(game_time, frame_number);
        }

        protected virtual void OnUpdate(GameTime game_time, Int64 frame_number) { }

        #endregion

        #region dispose
        // disposed?
        protected bool disposed = false;

        // dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        protected virtual void Dispose(Boolean disposing)
        {
            // have disposed?
            if (disposed)
                return;
            disposed = true;

            if (children != null)
            {
                foreach (Node node in children.Values)
                    node.Dispose(disposing);
            }

            // is call manually?
            if (disposing)
                UnloadUnmanagedContent();
        }

        protected virtual void UnloadUnmanagedContent() { }
        #endregion
    }
}
